package com.filldream.sun.core;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;

import com.filldream.sun.bean.calendar.*;
import com.filldream.sun.constants.FinalConstants;
import com.filldream.sun.bean.SunCalendar;

/**
 * 万年历工具类
 * @author RickSun
 * 2019-03-03 16:39
 */
public class LunarUtil {
    public LunarUtil() {
        throw new UnsupportedOperationException("cannot be instantiated");
    }

	//对应年月日的天干地支
	private static int[] lunar_info = {0x04bd8, 0x04ae0, 0x0a570, 0x054d5,
			0x0d260, 0x0d950, 0x16554, 0x056a0, 0x09ad0, 0x055d2, 0x04ae0,
			0x0a5b6, 0x0a4d0, 0x0d250, 0x1d255, 0x0b540, 0x0d6a0, 0x0ada2,
			0x095b0, 0x14977, 0x04970, 0x0a4b0, 0x0b4b5, 0x06a50, 0x06d40,
			0x1ab54, 0x02b60, 0x09570, 0x052f2, 0x04970, 0x06566, 0x0d4a0,
			0x0ea50, 0x06e95, 0x05ad0, 0x02b60, 0x186e3, 0x092e0, 0x1c8d7,
			0x0c950, 0x0d4a0, 0x1d8a6, 0x0b550, 0x056a0, 0x1a5b4, 0x025d0,
			0x092d0, 0x0d2b2, 0x0a950, 0x0b557, 0x06ca0, 0x0b550, 0x15355,
			0x04da0, 0x0a5d0, 0x14573, 0x052d0, 0x0a9a8, 0x0e950, 0x06aa0,
			0x0aea6, 0x0ab50, 0x04b60, 0x0aae4, 0x0a570, 0x05260, 0x0f263,
			0x0d950, 0x05b57, 0x056a0, 0x096d0, 0x04dd5, 0x04ad0, 0x0a4d0,
			0x0d4d4, 0x0d250, 0x0d558, 0x0b540, 0x0b5a0, 0x195a6, 0x095b0,
			0x049b0, 0x0a974, 0x0a4b0, 0x0b27a, 0x06a50, 0x06d40, 0x0af46,
			0x0ab60, 0x09570, 0x04af5, 0x04970, 0x064b0, 0x074a3, 0x0ea50,
			0x06b58, 0x055c0, 0x0ab60, 0x096d5, 0x092e0, 0x0c960, 0x0d954,
			0x0d4a0, 0x0da50, 0x07552, 0x056a0, 0x0abb7, 0x025d0, 0x092d0,
			0x0cab5, 0x0a950, 0x0b4a0, 0x0baa4, 0x0ad50, 0x055d9, 0x04ba0,
			0x0a5b0, 0x15176, 0x052b0, 0x0a930, 0x07954, 0x06aa0, 0x0ad50,
			0x05b52, 0x04b60, 0x0a6e6, 0x0a4e0, 0x0d260, 0x0ea65, 0x0d530,
			0x05aa0, 0x076a3, 0x096d0, 0x04bd7, 0x04ad0, 0x0a4d0, 0x1d0b6,
			0x0d250, 0x0d520, 0x0dd45, 0x0b5a0, 0x056d0, 0x055b2, 0x049b0,
			0x0a577, 0x0a4b0, 0x0aa50, 0x1b255, 0x06d20, 0x0ada0};



    //***********************获取中国农历和节气相关算法**************************/

	/**
	 * 获取年月日的天干地支
	 * @return 甲辰 甲戌 庚戌 List
	 */
	public static List<String> getGanZhi(Date date){
		List<Integer> ganzhiIndexs = initGanZhi(date);
		List<String> resultList = new ArrayList<>();
		for(Integer index : ganzhiIndexs){
			resultList.add( ganZhi(index) );
		}
		return resultList;
	}


	/**
	 * 获取生肖
	 * @param year	年份
	 * @param isLunar	天干地支叫法
	 * @return	生肖
	 */
	public static String getAnimalsYear(Integer year,Boolean isLunar) {
		if(isLunar){
			return FinalConstants.ZHI[(year - 4) % 12];
		}
		return FinalConstants.ANIMALS[(year - 4) % 12];
	}

	/**
	 * 获取农历日期
	 * @param date	日期
	 * @return	农历字符串
	 * 	例如：2019-03-03的农历为2019年一月廿七
	 */
	public static String getLunar(Date date) {
		Calendar calendar = Calendar.getInstance();
		calendar.setTime(date);
		return getLunar(calendar);
	}

	/**
	 * 获取24节气
	 * @param year	年份
	 * @param month 月份
	 * @param date 日期
	 * @return	节气名称
	 */
	public static String getTermName(int year, int month, int date) {
		Map<String, String> map = getYearTermMap(year);
		if (map == null || map.isEmpty()) {
			return null;
		}
		return map.get(getTermKey(month, date));
	}




	//***********************节假日相关算法**************************/

	/**
	 * 获取日期的工作状态
	 * @param date  日期
	 * @return
	 * 		0-正常工作日
	 * 		1-法定节假日
	 * 		2-节假日调休补班
	 * 		3-休息日
	 * 		异常失败为-1
	 */
	public static Integer getDayIsWork(Date date) {
		String workStr = UrlUtil.doGet("http://api.goseek.cn/Tools/holiday?date="+DateUtil.dateToStrng(date, "yyyyMMdd"),null);
		Work work = JsonUtil.fromJson(workStr, Work.class);
		if(work.getCode().equals("10000")) {
			return work.getData();
		}
		return -1;
	}


	/**
	 * 获取日期的节假日名称
	 * @param date	日期
	 * @return	例如：春节
	 * 无则返回null
	 */
	public static String getHolidayName(Date date) throws ParseException{
		Integer year = DateUtil.getTimeSlice(date, Calendar.YEAR);
		Integer month = DateUtil.getTimeSlice(date, Calendar.MONTH)+1;
		Integer day = DateUtil.getTimeSlice(date, Calendar.DAY_OF_MONTH);

		String lundar = getLunar(date);
		String cMonth = lundar.substring(lundar.length()-4,lundar.length()-2);
		String cDay = lundar.substring(lundar.length()-2,lundar.length());

//		URL url = ChAddressUtil.class.getResource("/file/holiday.txt");
		String jsonText = FileIOUtil.fileToString("/file/holiday.txt");
//		String jsonText = FileIOUtil.FileToString(new File(url.getFile()));

		List<Holidays> list = JsonUtil.fromJosnArray(jsonText, Holidays.class);
		String result = "";

		for(Holidays holiday : list) {
			String wheres = holiday.getWheres();
			String m = holiday.getMonth();
			String d = holiday.getDay();

			if(wheres.equals("final")) {
				if(month.toString().equals(m) && day.toString().equals(d)) {
					result += holiday.getName()+"+";
				}
				if(cMonth.equals(m) && cDay.equals(d) ) {
					result += holiday.getName()+"+";
				}
			}else {
				if(wheres.indexOf("afterJQ") > -1) {
					String value = wheres.split("-")[1].split(":")[0];
					Integer addNum = Integer.valueOf(wheres.split(":")[1]);
					Map<String, String> map = getYearTermMap(year);

					List<String> keyList = new ArrayList<String>();
					for(String key: map.keySet()){
						if(map.get(key).equals(value)){
							keyList.add(key);
						}
					}

					String holidayDate = year+keyList.get(0);//0808春分
					Date hDate = DateUtil.getDateByAddDay(DateUtil.stringToDate(holidayDate, "yyyyMMdd"), addNum);
					if(DateUtil.CompareDateSize(hDate, date) == 0) {
						result += holiday.getName() + "+";
					}

				}else if(wheres.indexOf(":") == 1) {
					String[] values = wheres.split(":");
					if(values[0].equals("0") && month.toString().equals(m)) {
						Calendar calendar = Calendar.getInstance();
						calendar.setTime(date);
						int weekSum = calendar.getActualMaximum(Calendar.WEEK_OF_MONTH);

						List<String> weekList = DateUtil.dayInWeekInMonth(date);
						boolean isLast = false;
						if(weekList.get(0).equals(String.valueOf(weekSum)) && weekList.get(1).equals(values[1])  && isLast==false) {
							result += holiday.getName() + "+";
							isLast = true;
						}
						weekSum-=1;
						if(weekList.get(0).equals(String.valueOf(weekSum)) && weekList.get(1).equals(values[1])  && isLast==false) {
							result += holiday.getName() + "+";
						}
					}else if(month.toString().equals(m)){
						List<String> weekList = DateUtil.dayInWeekInMonth(date);
						if(weekList.get(0).equals(values[0]) && weekList.get(1).equals(values[1])  ) {
							result += holiday.getName() + "+";
						}
					}

				}else if(wheres.equals("lastday")) {
					if(month.toString().equals(m)){
						Calendar calendar = Calendar.getInstance();
						calendar.setTime(date);
						if(calendar.getActualMaximum(Calendar.DAY_OF_MONTH) == day) {
							result += holiday.getName() + "+";
						}
					}
				}else if(wheres.equals("lastyear")) {
					String lastDay = getChinaDayString(monthDays(year-1, 12));
					if(cMonth.toString().equals(m)  && lastDay.equals(cDay) ){
						result += holiday.getName() + "+";
					}
				}
			}
		}
		if(result.length() < 1) {
			return null;
		}
		return result.substring(0,result.length()-1);
	}

    
    /**
     * 获取日期万年历
     * @param date
     * @return 返回SunCalendar类
     * 包含:星期、禁忌、事宜、农历、班休状态、节假日、传统节日、天干地支年月日、节气
     * @throws ParseException
     */
    public static SunCalendar  getDayInfo(Date date) throws ParseException{
    	String url = "https://sp0.baidu.com/8aQDcjqpAAV3otqbppnN2DJv/api.php?resource_id=6018&format=json&query="+DateUtil.dateToStrng(date, "yyyy年MM月");
		SunCalendar result = new SunCalendar();
    	String content = UrlUtil.doGet(url,null);
    	content = content.replaceAll("list\":", "hlist\":");
		SunLunar sunLunar = JsonUtil.fromJson(content, SunLunar.class);

		if(sunLunar == null || !sunLunar.getStatus().equals("0")){
			return null;
		}

		List<Data> dataList = sunLunar.getData();

		for(Data data : dataList){

			List<Almanac> almanacList = data.getAlmanac();
			List<Holiday> holidayList = data.getHoliday();
			for(Almanac almanac : almanacList){
				Date almanacDate = DateUtil.stringToDate(almanac.getDate(), "yyyy-MM-dd");
				if(almanacDate.equals(date)){
					result.setAvoid(almanac.getAvoid());
					result.setSuit(almanac.getSuit());
				}
			}

			for(Holiday holiday : holidayList){
				Date holidayDate = DateUtil.stringToDate(holiday.getFestival(), "yyyy-MM-dd");
				if(holidayDate.equals(date)) {
					result.setHolidayDesc(holiday.getDesc());
					result.setHolidayRest(holiday.getRest());
					result.setHolidayName(holiday.getName());
					List<Hlist> hlistList = holiday.getHlist();
					for(Hlist hlist : hlistList){
						Date hDate = DateUtil.stringToDate(hlist.getDate(), "yyyy-MM-dd");
						if(hDate.equals(date)){
							result.setIsWork(Integer.valueOf(hlist.getStatus()));
						}
					}
				}
			}

		}
		result.setLunar(getLunar(date));
		result.setGan(getGanZhi(date).toString());
		result.setWeek(DateUtil.dateToStrng(date, "E"));
		result.setTermName(getTermName(DateUtil.getTimeSlice(date, Calendar.YEAR),DateUtil.getTimeSlice(date, Calendar.MONTH),DateUtil.getTimeSlice(date, Calendar.DAY_OF_MONTH)));
		result.setChinaFestival(getHolidayName(date));
		if(result.getIsWork() == null){
			result.setIsWork(getDayIsWork(date));
		}
		return result;
    }
    

    

    




















	//*********************私有方法*********************

	/**
	 * 获取农历日期读法
	 * @param day	日期
	 * @return	农历读法
	 * 例如：1则返回"初一"
	 */
	private static String getChinaDayString(int day) {
		int n = day % 10 == 0 ? 9 : day % 10 - 1;
		if (day > 30)
			return "";
		if (day == 10)
			return "初十";
		else
			return FinalConstants.CHINESETEN[day / 10] + FinalConstants.CHINESENUMBER[n];
	}

	/**
	 * 将年月日转化为天干地支的显示方法
	 * @param index
	 * @return
	 */
	private  static String ganZhi(int index) {
		return FinalConstants.GAN[index % 10] + FinalConstants.ZHI[index % 12];
	}

	/**
	 * 初始化年月日对应的天干地支
	 * @param date
	 */
	private static List<Integer> initGanZhi(Date date) {
		int year_ganZhi;
		int month_ganZhi;
		int day_ganZhi;

		//获取现在的时间
//		Calendar calendar_now = Calendar.getInstance();
//		calendar_now.set(year, month - 1, day);
		long date_now = date.getTime();
		//获取1900-01-31的时间
		Calendar calendar_ago = Calendar.getInstance();
		calendar_ago.set(1900, 0 ,31);
		long date_ago = calendar_ago.getTime().getTime();
		//86400000 = 24 * 60 * 60 * 1000
		long days_distance = (date_now - date_ago) / 86400000L;
		float remainder = (date_now - date_ago) % 86400000L;
		//余数大于0算一天
		if (remainder > 0) {
			days_distance += 1;
		}
		//都是从甲子开始算起以1900-01-31为起点
		//1899-12-21是农历1899年腊月甲子日  40：相差1900-01-31有40天
		day_ganZhi = (int)days_distance + 39;
		//1898-10-01是农历甲子月  14：相差1900-01-31有14个月
		month_ganZhi = 14;
		int daysOfYear = 0;
		int i;
		for (i = 1900; i < 2050 && days_distance > 0; i++) {
			daysOfYear = yearDays(i);
			days_distance -= daysOfYear;
			month_ganZhi += 12;
		}
		if (days_distance < 0) {
			days_distance += daysOfYear;
			i--;
			month_ganZhi -= 12;
		}
		//农历年份
		int myYear = i;
		//1864年是甲子年
		year_ganZhi = myYear - 1864;
		//哪个月是闰月
		int leap = lunar_info[myYear - 1900] & 0xf;
		boolean isLeap = false;
		int daysOfLeapMonth = 0;
		for (i = 1; i < 13 && days_distance > 0; i++) {
			//闰月
			if (leap > 0 && i == (leap + 1) && !isLeap) {
				isLeap = true;
				if ((lunar_info[myYear - 1900] & 0xf) != 0) {
					daysOfLeapMonth = (lunar_info[myYear - 1900] & 0x10000) == 0 ? 29 : 30;
				} else {
					daysOfLeapMonth = 0;
				}
				--i;
			} else {
				daysOfLeapMonth = (lunar_info[myYear - 1900] & (0x10000 >> i)) == 0 ? 29 : 30;
			}
			//设置非闰月
			if (isLeap && i == (leap + 1)) {
				isLeap = false;
			}
			days_distance -= daysOfLeapMonth;
			if (!isLeap) {
				month_ganZhi++;
			}
		}
		if (days_distance == 0 && leap > 0 && i == leap + 1 && !isLeap) {
			--month_ganZhi;
		}
		if (days_distance < 0) {
			--month_ganZhi;
		}
		return Arrays.asList(year_ganZhi,month_ganZhi,day_ganZhi);
	}
    
    
    //农历算法
    /**
     * 获取农历日期
     * @param cal	日期
     * @return	农历字符串
     */
    private static String getLunar(Calendar cal) {
    	SimpleDateFormat chineseDateFormat = new SimpleDateFormat(
                "yyyy年MM月dd日");
    	
        @SuppressWarnings("unused")
        int yearCyl, monCyl, dayCyl;
        int leapMonth = 0;
        Date baseDate = null;
        int year;
        int month;
        int day;
        boolean leap;
        
        try {
            baseDate = chineseDateFormat.parse("1900年1月31日");
        } catch (ParseException e) {
            e.printStackTrace(); // To change body of catch statement use
                                    // Options | File Templates.
        }
        // 求出和1900年1月31日相差的天数
        int offset = (int) ((cal.getTime().getTime() - baseDate.getTime()) / 86400000L);
        dayCyl = offset + 40;
        monCyl = 14;
        // 用offset减去每农历年的天数
        // 计算当天是农历第几天
        // i最终结果是农历的年份
        // offset是当年的第几天
        int iYear, daysOfYear = 0;
        for (iYear = 1900; iYear < 2050 && offset > 0; iYear++) {
            daysOfYear = yearDays(iYear);
            offset -= daysOfYear;
            monCyl += 12;
        }
        if (offset < 0) {
            offset += daysOfYear;
            iYear--;
            monCyl -= 12;
        }
        // 农历年份
        year = iYear;
        yearCyl = iYear - 1864;
        leapMonth = leapMonth(iYear); // 闰哪个月,1-12
        leap = false;
        // 用当年的天数offset,逐个减去每月（农历）的天数，求出当天是本月的第几天
        int iMonth, daysOfMonth = 0;
        for (iMonth = 1; iMonth < 13 && offset > 0; iMonth++) {
            // 闰月
            if (leapMonth > 0 && iMonth == (leapMonth + 1) && !leap) {
                --iMonth;
                leap = true;
                daysOfMonth = leapDays(year);
            } else
                daysOfMonth = monthDays(year, iMonth);
            offset -= daysOfMonth;
            // 解除闰月
            if (leap && iMonth == (leapMonth + 1))
                leap = false;
            if (!leap)
                monCyl++;
        }
        // offset为0时，并且刚才计算的月份是闰月，要校正
        if (offset == 0 && leapMonth > 0 && iMonth == leapMonth + 1) {
            if (leap) {
                leap = false;
            } else {
                leap = true;
                --iMonth;
                --monCyl;
            }
        }
        // offset小于0时，也要校正
        if (offset < 0) {
            offset += daysOfMonth;
            --iMonth;
            --monCyl;
        }
        month = iMonth;
        day = offset + 1;
        
        return year + "年" + (leap ? "闰" : "") + getChinaMonth(month)
        + getChinaDayString(day);
    }
    
    /**
     * 获取农历月读法
     * @param month
     * @return
     */
    private static String getChinaMonth(int month) {
    	String resultMonth = FinalConstants.CHINESENUMBER[month - 1];
        if(resultMonth.equals("一")) {
        	resultMonth = "正";
        }else if(resultMonth.equals("十二")) {
        	resultMonth = "腊";
        }
        return resultMonth+"月";
    }
    
    /**
     * 农历年天数
     * @param year 年份
     * @return	天数
     */
    final private static int yearDays(int year) {
        int i, sum = 348;
        for (i = 0x8000; i > 0x8; i >>= 1) {
            if ((FinalConstants.LUNARINFO[year - 1900] & i) != 0)
                sum += 1;
        }
        return (sum + leapDays(year));
    }
    
    /**
     * 农历闰月天数
     * @param y	年份
     * @return	闰月天数
     */
    private static int leapDays(int y) {
        if (leapMonth(y) != 0) {
            if ((FinalConstants.LUNARINFO[y - 1900] & 0x10000) != 0)
                return 30;
            else
                return 29;
        } else
            return 0;
    }
    
    /**
     * 农历闰几月
     * @param y	年份
     * @return	闰月月份
     * 无返回0
     */
    final private static int leapMonth(int y) {
        return (int) (FinalConstants.LUNARINFO[y - 1900] & 0xf);
    }
  
    /**
     * 农历年份的某月的天数
     * @param y	年份
     * @param m	月份
     * @return	天数
     */
    final private static int monthDays(int y, int m) {
        if ((FinalConstants.LUNARINFO[y - 1900] & (0x10000 >> m)) == 0)
            return 29;
        else
            return 30;
    }
    //end农历算法
    
    
    //------------24节气算法-------------
    
    //节气D值
  	private static final double D = 0.2422;
  	//20世纪气节C值
  	private static final double[] C_20 = { 6.11, 20.84, 4.6295, 19.4599, 6.3826, 21.4155, 5.59, 20.888, 6.318, 21.86,
  			6.5, 22.2, 7.928, 23.65, 8.35, 23.95, 8.44, 23.822, 9.098, 24.218, 8.218, 23.08, 7.9, 22.6 };
  	//21世纪气节C值
  	private static final double[] C_21 = { 5.4055, 20.12, 3.87, 18.73, 5.63, 20.646, 4.81, 20.1, 5.52, 21.04, 5.678,
  			21.37, 7.108, 22.83, 7.5, 23.13, 7.646, 23.042, 8.318, 23.438, 7.438, 22.36, 7.18, 21.94 };

  	private static Map<Integer, Map<String, String>> termMap = new HashMap<Integer, Map<String, String>>();

	private static Map<String, String> getYearTermMap(int year) {
		double[] c = null;
		if (year > 1900 && year <= 2000) {
			c = C_20;
		} else if (year > 2000 && year <= 2100) {
			c = C_21;
		} else {
			throw new RuntimeException("不支持的年份:" + year + ",目前只支持1901年到2100年的时间范围");
		}
		// 从节气表中取
		Map<String, String> map = termMap.get(year);
		if (map != null) {
			return map;
		}
		// 节气表中无对应的节气数据，则计算生成
		synchronized (FinalConstants.TERM) {
			map = termMap.get(year);
			if (map == null) {
				int y = year % 100;
				map = new HashMap<String, String>();
				for (int k = 0; k < 24; k++) {
					// 计算节气日期，计算公式：[Y*D+C]-L
					int date = 0;
					if (k < 2 || k > 22) {
						date = (int) (y * D + c[k]) - (int) ((y - 1) / 4);
					} else {
						date = (int) (y * D + c[k]) - (int) (y / 4);
					}
					// 记录计算结果
					map.put(getTermKey(k / 2 + 1, date), FinalConstants.TERM[k]);
				}
				// 计算结果添加到节气表
				termMap.put(year, map);
			}
		}
		return map;
	}

	private static String getTermKey(int month, int date) {
		String key = String.valueOf(month);
		if (month < 10) {
			key = "0" + key;
		}
		if (date < 10) {
			key += "0";
		}
		key += date;
		return key;
	}
  	
    //end 24节气算法
}
